package com.lipaluma.field.validator.impl.numeric;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

import com.lipaluma.annotations.validations.ValidateDecimal;
import com.lipaluma.context.Context;
import com.lipaluma.field.validator.ValidationError;
import com.lipaluma.field.validator.impl.generic.AbstractConstantValidator;
import com.lipaluma.xml.validator.XmlConstantValidator;
import com.lipaluma.xml.validator.XmlValidator;

/**
 * Validate a decimal type field (Float, float, Double, double) to check if all value for the field is constant.<br/>
 * A value can be supplied or not. 
 * <ul>
 * <li>If the value is supplied, all values of the source must be equal to that value</li>
 * <li>If not, the first value encountered in the source will be the constant to respect for all others.</li>
 * </ul>
 * <p>
 * A precision may be mentioned to round the decimal value of the source at precision before the validation.
 * 
 * <p>
 * For Initialisation :
 * <ul>
 * 	<li>
 * 	<u><b>By annotations :</b></u> Use {@link ValidateDecimal}, with the attribute <code>constant = true</code>. <br/>
 * To supply a value, put <code>constantSupplied = true</code> and the value supplied in <code>constantValue</code> attribute.<br/>
 * The precision can be mentioned with the attribute <b>precision</b></li>
 * 	<li>
 * 	<u><b>By Xml :</b></u>
 * <ul>
 * <li>
 * See the example below to add a constant validator with a precision but without supplied value : <br/>
 * <code>
 * &lt;ToMap field="..."   ...><br/>
 * &nbsp;&nbsp;&nbsp;<b>&lt;constant precision="2"/></b><br/>
 * &lt;/ToMap>
 * </code>
 * <br/>
 * The precision is optional.<br/>
 * </li>
 * <li>
 * See this other example to add a constant validator without precision but with a supplied value : <br/>
 * <code>
 * &lt;ToMap field="..."   ...><br/>
 * &nbsp;&nbsp;&nbsp;<b>&lt;constant>12.515&lt;/constant></b><br/>
 * &lt;/ToMap>
 * </code>
 * <br/>
 * </li>
 * </li>
 * </ul>
 * @author Mario
 *
 */
public class DConstantValidator extends AbstractConstantValidator<Double> {

	private Integer precision;
	/**
	 * No value supplied without precision.
	 */
	public DConstantValidator() {}
	
	/**
	 * Specify a precision but no supplied value
	 * @param precision
	 */
	public DConstantValidator(int precision) {
		this.precision = precision;
	}
	/**
	 * Specify a supplied value without precision
	 * @param constant the constant supplied
	 */
	public DConstantValidator(double constant) {
		super(constant);
	}

	/**
	 * Specify a supplied value with precision
	 * @param constant the constant supplied
	 * @param precision
	 */
	public DConstantValidator(double constant, int precision) {
		super(constant);
		this.precision = precision;
	}

	@Override
	public boolean init(Context context, Field field) {
		ValidateDecimal annotation = field.getAnnotation(ValidateDecimal.class);
		if(annotation == null || !annotation.constant())
			return false;
		if(annotation.precision() >=0)
			precision = annotation.precision();
		if(annotation.constantSupplied()) {
			init(roundValueByPrecision(annotation.constantValue(), precision));
		}
		return true;
	}

	@Override
	public boolean init(Context context, Field field, XmlValidator xmlValidator) {
		XmlConstantValidator validator = (XmlConstantValidator) xmlValidator;
		this.precision = validator.getPrecision();
		if(validator.getConstantValue() != null) {
			Double value = validator.getConstantValue(Double.class);
			init(roundValueByPrecision(value, this.precision));
		}
		return true;
	}
	
	
	@Override
	public boolean validate(Object value, List<ValidationError> errors) {
		return super.validate(roundValueByPrecision(new Double(value.toString()), this.precision), errors);
	}
	
	private Double roundValueByPrecision(Double value, Integer precision) {
		if(precision != null)
			return new BigDecimal(String.valueOf(value)).setScale(precision, RoundingMode.HALF_DOWN).doubleValue();				
		else 
			return value;
	}
}
